package org.rc.dao;

import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.google.appengine.api.memcache.stdimpl.GCacheFactory;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;
import org.rc.model.*;
import com.googlecode.objectify.util.DAOBase;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author dokeeffe
 */
public class GenericDao<T> extends DAOBase {

    private Class persistentClass;
    protected Cache genericCache;
    private static final Logger LOGGER = LoggerFactory.getLogger(GenericDao.class);
    

    static {
        ObjectifyService.register(Athlete.class);
        ObjectifyService.register(Race.class);
        ObjectifyService.register(Result.class);
        ObjectifyService.register(ResultsFile.class);
        ObjectifyService.register(Statistics.class);
        ObjectifyService.register(AppUser.class);
        ObjectifyService.register(SearchTerm.class);
    }

    /**
     * Constructor. Sets up a generic 4hr expiry cache
     * @param persistentClass 
     */
    public GenericDao(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
        Map props = new HashMap();
        props.put(GCacheFactory.EXPIRATION_DELTA, 14400);//4hrs cache expiry
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            genericCache = cacheFactory.createCache(props);
        } catch (CacheException e) {
            LOGGER.error("error setting up cache", e);
        }
    }

    /**
     * Count entities (not recommended)
     * @return 
     */
    public long count() {
        return ofy().query(persistentClass).count();
    }
    
    /**
     * Loads from the datastore by id
     * @param id
     * @return 
     */
    public T get(Long id) {
        return (T) ofy().find(persistentClass, id);
    }

    /**
     * Loads all from the datastore
     * @return 
     */
    public List<T> getAll() {
        Query<T> q = ofy().query(persistentClass);
        return asList(q.fetch());
    }

    /**
     * Loads top n
     * @param n - number of entities to return
     * @param order - optional order field
     * @return
     */
    public List<T> getTop(int n, String order) {
        Query<T> q = ofy().query(persistentClass);
        q.limit(n);
        if(order!=null) {
            q.order(order);
        }
        return asList(q.fetch());
    }
    
    /**
     * Loads a 'page' of results from the cursor string passed. The cursor will be converted fromWebSafeString
     * @param results
     * @param cursor
     * @param pageSize
     * @return 
     */
    public String getAllFromCursor(List<T> results, String cursor, int pageSize) {
        Query<T> q = ofy().query(persistentClass);
        return getAllFromCursorForQuery(q,results,cursor,pageSize);
    }

    /**
     * Loads a page of results for the query and cursor passed. If cursor is null starts from the first entity. Returns a new cursor for the end point
     * @param q
     * @param results
     * @param cursor
     * @param pageSize
     * @return
     */
    protected String getAllFromCursorForQuery(Query q, List<T> results, String cursor, int pageSize) {
        if(cursor!=null) {
            q.startCursor(Cursor.fromWebSafeString(cursor));
        }
        q.limit(pageSize);
        QueryResultIterator<T> iterator = q.iterator();
        while (iterator.hasNext()) {
            results.add(iterator.next());
        }
        return iterator.getCursor().toWebSafeString();
    }
    
    /**
     * Puts an entity in the datastore
     * @param entity 
     */
    public void put(T entity) {
        ofy().put(entity);
    }

    /**
     * Delete an entity from the datastore
     * @param entity
     */
    public void delete(T entity) {
        ofy().delete(entity);
    }
    
    /**
     * Delete an entity from the datastore
     * @param key
     */
    public void delete(Key<T> key) {
        ofy().delete(key);
    }

    /**
     * Load all entities by property
     * @param propName
     * @param propValue
     * @return 
     */
    public List<T> listByProperty(String propName, Object propValue) {
        Query<T> q = ofy().query(persistentClass);
        q.filter(propName, propValue);
        return asList(q.fetch());
    }

    /**
     * Load one entity by property.
     * @param propName
     * @param propValue
     * @return 
     */
    public T getByProperty(String propName, Object propValue) {
        Query<T> q = ofy().query(persistentClass);
        q.filter(propName, propValue);
        return q.get();
    }

    /**
     * Load all keys for a property
     * @param propName
     * @param propValue
     * @return 
     */
    public List<Key<T>> listKeysByProperty(String propName, Object propValue) {
        Query<T> q = ofy().query(persistentClass);
        q.filter(propName, propValue);
        return asKeyList(q.fetchKeys());
    }

    protected List<T> asList(Iterable<T> iterable) {
        ArrayList<T> list = new ArrayList<T>();
        for (T t : iterable) {
            list.add(t);
        }
        return list;
    }

    protected List<Key<T>> asKeyList(Iterable<Key<T>> iterableKeys) {
        ArrayList<Key<T>> keys = new ArrayList<Key<T>>();
        for (Key<T> key : iterableKeys) {
            keys.add(key);
        }
        return keys;
    }
}
